/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.dx.util;

/**
 * Utilities for parsing hexadecimal text.
 */
public final class HexParser {

	/**
	 * This class is uninstantiable.
	 */
	private HexParser() {
		// This space intentionally left blank.
	}

	/**
	 * Parses the given text as hex, returning a {@code byte[]} corresponding to
	 * the text. The format is simple: Each line may start with a hex offset
	 * followed by a colon (which is verified and presumably used just as a
	 * comment), and then consists of hex digits freely interspersed with
	 * whitespace. If a pound sign is encountered, it and the rest of the line
	 * are ignored as a comment. If a double quote is encountered, then the
	 * ASCII value of the subsequent characters is used, until the next double
	 * quote. Quoted strings may not span multiple lines.
	 * 
	 * @param src
	 *            {@code non-null;} the source string
	 * @return {@code non-null;} the parsed form
	 */
	public static byte[] parse(String src) {
		int len = src.length();
		byte[] result = new byte[len / 2];
		int at = 0;
		int outAt = 0;

		while (at < len) {
			int nlAt = src.indexOf('\n', at);
			if (nlAt < 0) {
				nlAt = len;
			}
			int poundAt = src.indexOf('#', at);

			String line;
			if ((poundAt >= 0) && (poundAt < nlAt)) {
				line = src.substring(at, poundAt);
			} else {
				line = src.substring(at, nlAt);
			}
			at = nlAt + 1;

			int colonAt = line.indexOf(':');

			atCheck: if (colonAt != -1) {
				int quoteAt = line.indexOf('\"');
				if ((quoteAt != -1) && (quoteAt < colonAt)) {
					break atCheck;
				}

				String atStr = line.substring(0, colonAt).trim();
				line = line.substring(colonAt + 1);
				int alleged = Integer.parseInt(atStr, 16);
				if (alleged != outAt) {
					throw new RuntimeException("bogus offset marker: " + atStr);
				}
			}

			int lineLen = line.length();
			int value = -1;
			boolean quoteMode = false;

			for (int i = 0; i < lineLen; i++) {
				char c = line.charAt(i);

				if (quoteMode) {
					if (c == '\"') {
						quoteMode = false;
					} else {
						result[outAt] = (byte) c;
						outAt++;
					}
					continue;
				}

				if (c <= ' ') {
					continue;
				}
				if (c == '\"') {
					if (value != -1) {
						throw new RuntimeException("spare digit around "
								+ "offset " + Hex.u4(outAt));
					}
					quoteMode = true;
					continue;
				}

				int digVal = Character.digit(c, 16);
				if (digVal == -1) {
					throw new RuntimeException("bogus digit character: \"" + c
							+ "\"");
				}
				if (value == -1) {
					value = digVal;
				} else {
					result[outAt] = (byte) ((value << 4) | digVal);
					outAt++;
					value = -1;
				}
			}

			if (value != -1) {
				throw new RuntimeException("spare digit around offset "
						+ Hex.u4(outAt));
			}

			if (quoteMode) {
				throw new RuntimeException("unterminated quote around "
						+ "offset " + Hex.u4(outAt));
			}
		}

		if (outAt < result.length) {
			byte[] newr = new byte[outAt];
			System.arraycopy(result, 0, newr, 0, outAt);
			result = newr;
		}

		return result;
	}
}
